package org.log4jconfig.java.editor.template;

import java.util.List;
import java.util.Map;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
import org.eclipse.jdt.internal.corext.template.java.JavaContext;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.TemplateException;
import org.eclipse.swt.graphics.Point;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PlatformUI;
import org.log4jconfig.java.editor.Log4jConfigJavaEditorIntegrationPlugin;
import org.log4jconfig.java.editor.util.Logger;

@SuppressWarnings("restriction")
public class LoggerDeclarationHandler extends AbstractHandler {

	public static final String LOGGER_DECLARATION_TEMPLATE = Log4jConfigJavaEditorIntegrationPlugin.PLUGIN_ID
			+ ".loggerDeclarationTemplate";

	/**
	 * FIXME : needs more code here : - check the field name - check the *full*
	 * field type (ex : org.apache.log4j.Logger if direct Log4j API usage) -
	 * check that the field declaration matches the expected declaration : ex :
	 * (...).LogManager.getLogger(<classname>) if incomplete match, decide
	 * whether : - do nothing - refactor variable name - modify declaration (ex:
	 * LogManager.getLogger("foo") to LogManager.getLogger(<classname>) -other ?
	 */
	public Object execute(ExecutionEvent event) throws ExecutionException {
		IEditorPart editorPart = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
		// TODO : use the code below ?
		// EditorUtility.getActiveEditorJavaInput();
		try {
			if (editorPart instanceof CompilationUnitEditor) {
				CompilationUnitEditor compilationUnitEditor = (CompilationUnitEditor) editorPart;
				Point position = compilationUnitEditor.getViewer().getSelectedRange();
				ICompilationUnit compilationUnit = (ICompilationUnit) JavaUI
						.getEditorInputJavaElement(compilationUnitEditor.getEditorInput());
				IDocument document = compilationUnitEditor.getDocumentProvider().getDocument(
						compilationUnitEditor.getEditorInput());
				// template resolution
				String template = evaluateTemplate(position, compilationUnit, document);
				if (template != null && template.length() > 0) {
					SimpleName loggerName = getTemplateLoggerName(template);
					if (needsToInsert(position, compilationUnit, loggerName)) {
						// insert the template at the beginning of the type
						// declaration, as first field
						IField field = insertFieldDeclaration(compilationUnit, position.x, template, new NullProgressMonitor());
						formatFieldInDocument(field, document);
						addImportIfRequired(compilationUnit, "org.apache.log4j.Logger");
						addImportIfRequired(compilationUnit, "org.apache.log4j.LogManager");
					}
				}
			}
		} catch (Exception e) {
			Logger.error("Failed to insert logger declaration", e);
			throw new ExecutionException("Failed to insert logger declaration");
		}
		return null;
	}

	protected IField insertFieldDeclaration(ICompilationUnit compilationUnit, int x, String contents,
			IProgressMonitor monitor) throws JavaModelException, MalformedTreeException, BadLocationException {
		IJavaElement targetElement = compilationUnit.getElementAt(x);
		while (targetElement != null && targetElement.getElementType() != IJavaElement.TYPE) {
			targetElement = targetElement.getParent();
		}
		if (targetElement == null) {
			Logger.warn("Target type was not found. Unable to add logger declaration.");
		}
		IType[] types = compilationUnit.getAllTypes();
		IType type = null;
		for (IType t : types) {
			if (t.getElementName().equals(targetElement.getElementName())) {
				type = t;
				break;
			}
		}
		if (type != null) {
			IField[] fields = type.getFields();
			IField sibling = null;
			if (fields.length > 0) {
				sibling = fields[0];
			}
			// contents = contents.trim(); // does not solve the issue below...
			return type.createField(contents, sibling, false, monitor);
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	protected void formatFieldInDocument(IField field, IDocument document) throws MalformedTreeException,
			BadLocationException, JavaModelException {
		
		ISourceRange sourceRange = field.getSourceRange();
		// take default Eclipse formatting options
		Map<String, String> options = DefaultCodeFormatterConstants.getEclipseDefaultSettings();
		// initialize the compiler settings to be able to format 1.5 code
		options.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.getOption(JavaCore.COMPILER_COMPLIANCE));
		options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore
				.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM));
		options.put(JavaCore.COMPILER_SOURCE, JavaCore.getOption(JavaCore.COMPILER_SOURCE));
		options.put(DefaultCodeFormatterConstants.FORMATTER_BLANK_LINES_BEFORE_FIELD, "1");
		CodeFormatter codeFormatter = ToolFactory.createCodeFormatter(options);
		TextEdit replacement = codeFormatter.format(
				CodeFormatter.K_COMPILATION_UNIT + CodeFormatter.F_INCLUDE_COMMENTS, document.get(), sourceRange
						.getOffset(), sourceRange.getLength(), 0, null);
		replacement.apply(document);

	}

	private boolean needsToInsert(Point position, ICompilationUnit javaElement, SimpleName loggerName)
			throws JavaModelException {
		for (IType type : javaElement.getAllTypes()) {
			ISourceRange sourceRange = type.getSourceRange();
			if (position.x > sourceRange.getOffset()
					&& position.x < (sourceRange.getOffset() + sourceRange.getLength())) {
				IField field = type.getField(loggerName.getFullyQualifiedName());
				if (field != null && field.exists()) {
					return false;
				}
			}
		}
		return true;
	}

	@SuppressWarnings("unchecked")
	private SimpleName getTemplateLoggerName(String template) {
		Block block = parse(template);
		VariableDeclarationStatement stmt = (VariableDeclarationStatement) block.statements().get(0);
		// Type loggerType = stmt.getType();
		SimpleName loggerName = null;
		for (VariableDeclarationFragment fragment : (List<VariableDeclarationFragment>) (stmt.fragments())) {
			if (fragment.getName() != null) {
				loggerName = fragment.getName();
				break;
			}
		}
		return loggerName;
	}

	protected String evaluateTemplate(Point position, ICompilationUnit javaElement, IDocument document)
			throws BadLocationException, TemplateException {
		Template template = getTemplateFromStore();
		TemplateContextType contextType = JavaPlugin.getDefault().getTemplateContextRegistry().getContextType(
				template.getContextTypeId());
		JavaContext templateContext = new JavaContext(contextType, document, position.x, 0, javaElement);
		templateContext.setForceEvaluation(true);
		String result = templateContext.evaluate(template).getString();
		return result;
	}

	protected Template getTemplateFromStore() {
		Template template = JavaPlugin.getDefault().getTemplateStore().findTemplateById(
				LoggerDeclarationHandler.LOGGER_DECLARATION_TEMPLATE);
		if (template == null) {
			Logger.error("Couldn't find template identified by '"
					+ LoggerDeclarationHandler.LOGGER_DECLARATION_TEMPLATE + "'");
		}
		return template;
	}

	protected void addImportIfRequired(ICompilationUnit javaElement, String fullyQualifiedName)
			throws JavaModelException {
		IImportDeclaration declaration = javaElement.getImport(fullyQualifiedName);
		if (!declaration.exists()) {
			javaElement.createImport(fullyQualifiedName, null, new NullProgressMonitor());
		}
	}

	protected Block parse(String content) {
		ASTParser parser = ASTParser.newParser(AST.JLS3);
		parser.setKind(ASTParser.K_STATEMENTS);
		parser.setSource(content.toCharArray()); // set source
		// parser.setResolveBindings(true); // we need bindings later on
		return (Block) parser.createAST(new NullProgressMonitor()); // parse
	}

}
